package br.upe.dsc.caeto.utils.xml;

import java.io.File;
import java.io.FileWriter;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;

import br.upe.dsc.caeto.core.CaetoManager;
import br.upe.dsc.caeto.core.ElementNotFoundException;
import br.upe.dsc.caeto.core.InvalidElementException;
import br.upe.dsc.caeto.core.application.Application;
import br.upe.dsc.caeto.core.cacheunit.CacheUnit;
import br.upe.dsc.caeto.core.cacheunit.CacheUnitExplorer;
import br.upe.dsc.caeto.core.cacheunit.CacheUnitSimple;
import br.upe.dsc.caeto.core.hierarchy.Hierarchy;
import br.upe.dsc.caeto.core.project.Project;
import br.upe.dsc.caeto.simulation.CacheResult;
import br.upe.dsc.caeto.simulation.Result;

public class ResultsXml {
    private String separator;
	
    public ResultsXml() {
    	separator = System.getProperties().getProperty("file.separator");
    }

    public void write(Document document, String resultFile) {
    	File file = new File(resultFile);
		try {
			FileWriter writer = new FileWriter(file, false);
			XMLOutputter outputter = new XMLOutputter(org.jdom.output
					.Format.getPrettyFormat());
			outputter.output(document, writer);
		} catch (java.io.IOException e) {
			System.out.println("ERROR:");
			System.out.print("ChartXml class, ");
			System.out.println("error in write method -> IOException.");
			//e.printStackTrace();
		}    	
    }
    
	public void write(Application application, Result result) {
		File file;
		Document document = null;
		CaetoManager manager = CaetoManager.getInstance();
		String resultFile = "";
		
		try {
			resultFile = manager.search(application.getProjectName())
				.getPath() + separator + application.getName() + "-results.xml";
		} catch (ElementNotFoundException e) {
			e.printStackTrace();
		}

		file = new File(resultFile);
		
		if (file.exists()) {
			document = read(resultFile);
			document.getRootElement().addContent(createChildElement(result));
		}
		else {
			Element rootElement = new Element("simulationResults");
			rootElement.setAttribute("project", application.getProjectName());
			rootElement.setAttribute("application", application.getName());
			rootElement.addContent(createChildElement(result));
			document = new Document(rootElement);
		}

		try {
			FileWriter writer = new FileWriter(file, false);
			XMLOutputter outputter = new XMLOutputter(org.jdom.output
					.Format.getPrettyFormat());
			outputter.output(document, writer);
		} catch (java.io.IOException e) {
			System.out.println("ERROR:");
			System.out.print("ProjectXml class, ");
			System.out.println("error in write method -> IOException.");
			//e.printStackTrace();
		}
	}
	
	public Document read(String chartFile) {
		Document document = null;
		File file = new File(chartFile);

		if (file.exists()) {
			SAXBuilder builder = new SAXBuilder();

			try {
				document = builder.build(file);
			}
			catch (java.io.FileNotFoundException e) {
				e.printStackTrace();
			}
			catch (java.io.IOException e) {
				e.printStackTrace();
			}
			catch (org.jdom.JDOMException e) {
				e.printStackTrace();
			}
		}

		return document;
	}
	
	public Element createChildElement(Result result) {
		Element resultElement = new Element("results");

		Element tagHierarchy = new Element("hierarchy");
		Element tagTensaoAlimentacao = new Element("tensaoAlimentacao");
		Element tagTransistorTechnology = new Element("transistorTechnology");
		
		resultElement.addContent(tagHierarchy);

		tagHierarchy.setAttribute("name", result.getHierarchy().getName());
		tagHierarchy.addContent(tagTensaoAlimentacao);
		tagHierarchy.addContent(tagTransistorTechnology);
		tagTensaoAlimentacao.addContent(result.getHierarchy()
									.getTensaoAlimentacao());
		tagTransistorTechnology.addContent(result.getHierarchy()
							   		.getTransistorTechnology());

		/*Adiciona os resultados de energia e ciclos*/
		Map<String, double[]> map = result.getResultMap();
		for (String key : map.keySet()) {
			Element currResult = new Element("result");
			currResult.setAttribute("cacheType", key);
			Element tagEnergy = new Element("totalEnergy");
			Element tagCycles = new Element("totalCycles");
			
			double[] tmp = map.get(key);
			tagEnergy.addContent(String.valueOf(tmp[0]));
			tagCycles.addContent(String.valueOf(tmp[1]));
			
			currResult.addContent(tagEnergy);
			currResult.addContent(tagCycles);
			
			resultElement.addContent(currResult);
		}
		
		
		Iterable<CacheUnit> iterator = result.getHierarchy().getCacheUnits();
		CacheUnitSimple simple;
		CacheUnitExplorer explorer;
		
		for (CacheUnit tmp : iterator) {
			if (tmp instanceof CacheUnitSimple) {
				/*Adiciona a hierarquia referente a simulação*/
				Element tagCacheUnit = new Element("cacheUnit");
				Element tagCacheSize = new Element("cacheSize");
				Element tagLineSize = new Element("lineSize");
				Element tagAssociativity = new Element("associativity");
				tagCacheUnit.setAttribute("type", "CacheUnitSimple");
				
				simple = (CacheUnitSimple) tmp;
				String name = simple.getName();

				tagCacheUnit.setAttribute("name", name);
				tagCacheSize.addContent(String.valueOf(simple.getTamanho()));
				tagLineSize.addContent(String.valueOf(simple.getTamLinha()));
				tagAssociativity.addContent(
						String.valueOf(simple.getAssociatividade()));

				tagCacheUnit.addContent(tagCacheSize);
				tagCacheUnit.addContent(tagLineSize);
				tagCacheUnit.addContent(tagAssociativity);

				tagHierarchy.addContent(tagCacheUnit);
				
				/*Adiciona as informacoes da simulacao referentes a CacheUnit*/
				Element cacheResult = new Element("cache");
				cacheResult.setAttribute("name", name);
			
				String[] elementName = {
					"hits", "accessNumber", "misses", "missRate", "acessTime",
					"pdyn", "pleak", "edyn", "eleak"
				};
				Element[] elements = new Element[elementName.length];
				
				for (int i = 0; i < elements.length; i++) {
					elements[i] = new Element(elementName[i]);
					cacheResult.addContent(elements[i]);
				}
				
				elements[0].addContent(String.valueOf(result
						.getCacheResult(name).getHits()));
				elements[1].addContent(String.valueOf(result
						.getCacheResult(name).getAccessNumber()));
				elements[2].addContent(String.valueOf(result
						.getCacheResult(name).getMisses()));
				elements[3].addContent(String.valueOf(result
						.getCacheResult(name).getMissRate()));
				elements[4].addContent(String.valueOf(result
						.getCacheResult(name).getAcessTime()));
				elements[5].addContent(String.valueOf(result
						.getCacheResult(name).getPdyn()));
				elements[6].addContent(String.valueOf(result
						.getCacheResult(name).getPleak()));
				elements[7].addContent(String.valueOf(result
						.getCacheResult(name).getEdyn()));
				elements[8].addContent(String.valueOf(result
						.getCacheResult(name).getEleak()));
			
				resultElement.addContent(cacheResult);
			}
		}

		return resultElement;
	}
	
    public List<Result> getSimulationResult(Document document) {
    	Element rootElement = document.getRootElement();
    	List<Result> list = new LinkedList<Result>();
    	List<Element> results = rootElement.getChildren();

    	for (Element result : results) {
    		list.add(buildResultObject(result));
	    }
    
    	return list;
    }
    
	private Result buildResultObject(Element resultElement) {
		Result result;
		
    	Hierarchy hierarchy;
    	CacheUnitSimple cacheSimple;
    	String hierarchyName, tensaoAlimentacao, transistorTechnology;
    	int associativity, lineSize, size;
    	float hits, accessNumber, misses;
    	double missRate, acessTime, pdyn, pleak, edyn, eleak;
    	
    	hierarchyName = resultElement.getChild("hierarchy")
    			.getAttributeValue("name");
    	tensaoAlimentacao = resultElement.getChildText("tensaoAlimentacao");
    	transistorTechnology = resultElement.getChildText(
    			"transistorTechnology");
    	
    	hierarchy = new Hierarchy(
    			hierarchyName, tensaoAlimentacao, transistorTechnology);

    	List<Element> cacheUnits = resultElement.getChild("hierarchy")
    			.getChildren("cacheUnit");
    	
    	for (Element cache : cacheUnits) {
    		if (cache.getAttributeValue("type").equals("CacheUnitSimple")) {
    			associativity = Integer.parseInt(
    					cache.getChildText("associativity"));
    			lineSize = Integer.parseInt(cache.getChildText("lineSize"));
    			size = Integer.parseInt(cache.getChildText("cacheSize"));

    			cacheSimple = new CacheUnitSimple(
    					cache.getAttributeValue("name"),
    					associativity, lineSize, size);
    			try {
    				hierarchy.addCacheUnit(cacheSimple);
    			}
    			catch (InvalidElementException e) {
    				System.out.println(e.getMessage());
    			}
    		}
    	}

    	result = new Result(hierarchy);
    	
    	/*Energia e ciclos obtidos na simulacao*/
    	List<Element> results = resultElement.getChildren("result");
    	for (Element currResult : results) {
    		result.addResult(currResult.getAttributeValue("cacheType"),
    				Double.parseDouble(currResult.getChildText("totalEnergy")),
    				Double.parseDouble(currResult.getChildText("totalCycles")));    				
    	}

    	/*Resultados das caches*/
    	List<Element> cacheResults = resultElement.getChildren("cache");
    	CacheResult cacheResult;
    	for (Element cache : cacheResults) {
	    	hits = Float.parseFloat(cache.getChildText("hits"));
	    	accessNumber = Float.parseFloat(cache.getChildText("accessNumber"));
	    	misses = Float.parseFloat(cache.getChildText("misses"));
	    	missRate = Double.parseDouble(cache.getChildText("missRate"));
	    	acessTime = Double.parseDouble(cache.getChildText("acessTime"));
	    	pdyn = Double.parseDouble(cache.getChildText("pdyn"));
	    	pleak = Double.parseDouble(cache.getChildText("pleak"));
	    	edyn = Double.parseDouble(cache.getChildText("edyn"));
	    	eleak = Double.parseDouble(cache.getChildText("eleak"));
	    	
	    	cacheResult = new CacheResult(cache.getAttributeValue("name"),
	    			hits, accessNumber, misses, missRate,
	    			acessTime, pdyn, pleak, edyn, eleak);
	    	
	    	result.addCacheResult(cacheResult);
    	}

    	return result;
    }
}
